/*
 * Copyright (c) 2007 Compl Yue Still, All Rights Reserved.
 */

package hbi.wire.java.host;

import hbi.domain.java.JavaGateway;
import hbi.task.TaskAgent;
import hbi.task.java.TaskAgentLifeScript;
import hbi.wire.ProtocolError;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;

public class HostEnd
{

    @SuppressWarnings("unchecked")
    public final JavaGateway gateway;

    public final int requestedControllerVersion;

    public HostEnd(JavaGateway<?, ?> gateway, int requestedControllerVersion)
    {
        this.gateway = gateway;
        this.requestedControllerVersion = requestedControllerVersion;
    }

    @SuppressWarnings("unchecked")
    protected void hostTaskAgent(final TaskAgentLifeScript agentScript)
        throws IOException
    {
        try
        {
            gateway.localDomain.hostExecution(requestedControllerVersion,
                new Runnable()
                {
                    public void run()
                    {
                        try
                        {
                            agentScript.liveUp(gateway);
                        }
                        catch (Throwable e)
                        {
                            e.printStackTrace();
                            gateway.localDomain
                                .retryExecution(e, agentScript.countRetry(),
                                    requestedControllerVersion, this);
                        }
                    }
                });
        }
        catch (RuntimeException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            throw new ProtocolError(e);
        }
    }

    private final List<ResolvedType> resolvedTypes = new ArrayList<ResolvedType>();

    private ByteBuffer inBuffer = ByteBuffer.allocate(5000);

    private IncomingTag incomingTag = null;

    protected void resolveType(String typeName) throws IOException
    {
        try
        {
            typeName = typeName.replace("::", ".");
            Class<?> klass = Class.forName(typeName, true, this.getClass()
                .getClassLoader());
            if (!TaskAgent.class.isAssignableFrom(klass))
                throw new ProtocolError("Non-agent type: " + typeName);
            this.resolvedTypes.add(new ResolvedType(klass));
        }
        catch (ClassNotFoundException e)
        {
            throw new ProtocolError("Unknown type: " + typeName);
        }
    }

    protected ResolvedType getResolvedType(int typeIndex) throws ProtocolError
    {
        try
        {
            ResolvedType type = resolvedTypes.get(typeIndex);
            if (type == null)
                throw new ProtocolError("Invalid Type @ " + typeIndex);
            return type;
        }
        catch (Exception e)
        {
            throw new ProtocolError(e);
        }
    }

    private boolean closedByPeer = false;

    public boolean isClosedByPeer()
    {
        return closedByPeer;
    }

    public void readChannel(SocketChannel ch) throws IOException
    {
        if (closedByPeer) return;

        closedByPeer = (ch.read(inBuffer) < 0);

        inBuffer.flip();
        try
        {
            tag_scan: while (inBuffer.hasRemaining())
            {
                if (incomingTag == null)
                {
                    int tag = inBuffer.get() & 0xFF;
                    switch (tag)
                    // this switch defines top-level tags
                    {
                    case 't': // type constant
                        incomingTag = new TypeConstant(this);
                        break;
                    case 'a': // task agent life script
                        incomingTag = new TaskScript(this);
                        break;
                    case 'A': // streaming task agent life script
                        throw new RuntimeException("Not implemented yet");
                    case 0x7F: // DEL
                        resolvedTypes.clear();
                        continue tag_scan;
                    default:
                        throw new ProtocolError("Bad tag: " + tag);
                    }
                }
                if (incomingTag.processIncomingData(inBuffer))
                {
                    incomingTag = null;
                }
            }

            if (closedByPeer && incomingTag != null)
            // insufficient input data to finish current tag
                throw new ProtocolError("Incomplete tag");
        }
        finally
        {
            inBuffer.compact();
        }
    }

}
